根據 Web Almanac 2022 年的統計,網頁的圖片大小佔整體網頁檔案大小,中位數超過 6 成。而 JavaScipt 則佔近 3 成。
(圖片來源:https://almanac.httparchive.org/en/2022/page-weight#content-type-and-file-formats)
所以在 Next.js 13 效能優化的第一章,我們來關注兩點:
今天先來討論第一點,Next 針對圖片載入優化提供的 sloution:
當頁面載入圖片時,常會碰到幾個問題:
圖片數量多,載入速度慢:
假如沒特別處理,瀏覽器預設會等所有圖片都載入完成後才顯示頁面。因此當頁面圖片很多可能會影響到網頁載入速度。
於是很多開發者會採用 lazy loading 的方式,讓瀏覽器只載入目前 viewport 用到的圖片,而不用等到所有圖片都下載完後才能顯示畫面。
假如要做圖片的 lazy loading,可以使用 Web API 中的 Intersection Observer API。網路上已經很多相關教學,這邊就不多做介紹,有興趣了解的讀者可以直接搜尋「圖片 lazy loading」,或「Intersection Observer API」。
Layout Shift:
圖片載入完成後,推擠頁面其他元素,造成頁面 layout 改變。
( 圖片來源:https://productiveshop.com/cumulative-layout-shift/ )
layout shift 也是 Google 搜尋排名的其中一項標準 - Cumulative Layout Shift (CLS),所以有些網頁會預留圖片的空間,或盡量使用 transform 等不會影響 layout 的方式顯示圖片。
圖片檔案大,載入時間長:
有些網頁會特別將 PNG、JPEG 等圖片轉成 WebP、AVIF 等新型態的圖片格式,降低圖片檔案大小,且不影響圖片品質,以優化 UX 和 SEO。
但如同前面提到的諸多案例,每項優化都需要開發者額外花時間精力去處理,因此貼心的 Vercel 提供了一個 component next/image
,在底層幫你做好以上優化。
<Image>
Next 延伸了 HTML <image>
元素,提供一個效能更優的 component - next/image
。
優化內容有幾項:
要如何使用呢?
類似 <img>
的使用方式,我們可以從 next/image
import <Image>
後,在 src 指定圖片檔案路徑:
import Image from 'next/image';
import heroImage from './heroImage.png';
export default function Page() {
return (
<Image src={heroImage} alt='hero image />
);
}
自動轉檔
可以從上面 sample code 看到,heroImage 的格式原本是 png,但假設進到頁面另存這張圖檔,會發現下載的圖檔格式為 WebP。因為使用 <Image>
,Next 會自動幫你轉圖片格式。
Next 會依據 request header 中,Accept
array 的順序來決定轉檔的格式。假如想自訂格式順位,可以在 next.config.js
中設定:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
formats: ['image/avif', 'image/webp'],
},
};
module.exports = nextConfig;
這樣假如瀏覽器支援 AVIF,<Image>
的圖片就會優先轉為 AVIF。
自動設定圖片尺寸
假如使用本機圖檔,Next 會自動根據圖檔尺寸設定 <img>
的 width 和 height。為什麼要特別設定 width 跟 height 呢?為了預留位置給圖片,防止 layout shift。
假設我們在圖片下加一個 element:
import Image from 'next/image';
import heroImage from './heroImage.png';
export default function Page() {
return (
<>
<Image src={heroImage} alt='hero image' />
<h1>一起來欣賞美麗的風景</h1>
</>
);
}
可以發現圖片還載入完成前,網頁預留了圖片的區塊:
打開網頁原始碼,也可以發現 <img>
帶有 width 和 height 兩個 attributes。
但這時你可能會想:圖片載入成功前,畫面一塊白白的很奇怪,有辦法先顯示一個模糊的版本嗎?
blur-up placeholder<Image>
可以帶一個 placehoder prop。加入 placeholder='blur'
就可以在圖片載完前先顯示模糊的版本:
<Image src={heroImage} alt='hero image' placeholder='blur' />
next/image 不支援動態圖片匯入,例如
await import()
或require()
預設 lazy loading
檢視網頁原始碼可發現,Next 幫我們在 <img>
加上loading="lazy"
的 attribute。
所以當圖片接近 viewport 時,瀏覽器才會載入圖片:
假如想捨棄 lazy loading,可以將 loading
prop 改為 eager
:
/* 不會 lazy loading */
<Image src={heroImage} alt='hero image' loading='eager'/>
優先載入
假如頁面 LCP element 是一張圖片,我們可以在它的 <Image>
加上 priority
prop,讓這張圖片優先載入。
假如 <Image>
的 priority 為 true,則會取消 lazy loading,優先載入:
export default function Page() {
return (
<>
<Image src={image1} alt='image 1' />
{/* 讓 image2 優先載入 */}
<Image src={image2} alt='image2' priority />
<>
);
}
假如圖片不是從本機匯入 (ex: 雲端),要做幾項設定:
須在 next.config.js
中設定支援的圖片來源資訊:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [{
protocol: 'https',
hostname: 's3.amazonaws.com',
port: '',
pathname: '/my-bucket/**',
}],
},
};
module.exports = nextConfig;
須手動設定 width 跟 height:
width 與 height 主要目的是預留位置給圖片,避免 layout shift,不會影到圖片顯示比例。
假設 width 小於原圖的寬,則圖片會依照設定的 width 搭配對應的高顯示。比方說我的圖片比例為 4:3,尺寸為 800 * 600,假如我設定 <Image>
的 width 與 height 都為 400,那 loading 時預留的空間會是 400x400,最終圖片使寸會是 400x300。
如果不知道原圖的尺寸該怎麼辦?我們可以在 <Image>
包一層父元素,並在<Image>
加上 fill
這個 prop,來讓圖片填滿父元素
但因為加上 fill
後,圖片會是 position absolute,必須讓父元素為 position relative:
{/* 父層必須是 position relative */}
<div className='w-[500px] h-[500px] relative'>
<Image
src='...'
alt='hero image'
fill
/>
</div>
以上述例子,圖片 loading 時預留的空間,和最終顯示的尺寸都會是 500x500。
也可以使用 sizes
和 style
進一步調整圖片尺寸:
<Image
src='...'
alt='hero image'
fill
sizes="100vw"
style={{
objectFit: 'cover',
}}
/>
須手動設定 blurDataURL
remote 圖片使用 placeholder="blur"
,須提供 placeholder 圖片的 Data URL。
篇幅關係不多做說明,假如想使用簡單色塊,可以使用一些現成服務生成 Data URL:
<Image
src='...'
alt='hero image'
placeholder='blur'
blurDataURL='data:...'
width={500}
height={500}
/>
假如要搭配 background-image、image-set、canvas 中的
context.drawImage()
,或是<picture>
,沒辦法直接使用<Image>
,v13.5 有推出一個實驗 function:unstable_getImgProps()
,有需要可參考官方文件。
還記得我們我們在 Day 07 的開頭,我們提到 Next.js 13 的更新主要有三大重點:
Compiler Infrastructure 的優化我們在 Day 07 介紹了 Turbopack;Rendering Infrastructure 的優化我們介紹了新的路由架構 App Router,和新的 components 生成模式 Server Components。
而 Component Toolkit,則是 Next 有針對部分靜態文件提供了「優化版本」的 components,next/image
的 <Image>
就是其中之一。
除了圖片以外,Next 也有提供載入字型的優化。但因為篇幅考量,暫時就先不介紹。有需要的讀者可以參考官方文件:
最後做個小補充:
Web Almanac 2022 的報告,假如看網頁單一 reponse 中各類型檔案的大小,則是影片佔比最大。
所以我就查了一下,Next 有沒有針對影片優化,提供類似 <Image>
的 component。發現 2020 年有人發 issue 建議過官方,但時至今日官方還沒任何回覆。不過底下有些留言有提供在 Next 做影片效能優化的方法,有需要的讀者可以參考 issue!
以上就 Next 針對圖片優化的介紹,明天會接著討論 JavaScript 載入的優化。
謝謝大家耐心的閱讀,我們明天見!